scope以及scopechain

###1 作用域以及作用域链

1 .1作用域

  • 在JavaScript中,我们可以将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。

    这里的标识符,指的是变量名或者函数名

  • JavaScript中只有全局作用域与函数作用域

  • 作用域与执行上下文是完全不同的两个概念。

    JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。

    • 编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定以及语法分析,词法分析,可执行的代码生成;
    • 执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建以及垃圾回收;

1.2 作用域链

作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

  • 作用域链在函数执行之前就已经创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var cs = "zero";
function foo(){
var cs = "one" ;
function innerFoo1 (){
console.log(cs);
}
return innerFoo1;
}
//作用域链 全局作用域---> foo函数作用域---> innerFoo1函数作用域,当函数innerFoo1执行的时候,会顺着这条作用域链去找变量,一层一层向上找,直到全局变量对象处,如果找不到,则会抛出 未定义异常
var ret = foo(); //这个返回的是innerFoo1函数 innerFoo1函数执行的时候,现在innerFoo1函数作用域里面找cs变量,找不到的时候,向上一层foo函数作用域找cs变量,这个时候找到了 one,然后输出,结束函数执行;
ret(); //one
//如果作用域链是在函数执行的时候才创建,那么应该是如下作用域链
//全局作用域--->foo()作用域(其实就是innerFoo1作用域,foo函数执行了)--->
//ret()执行后应该输出zero,很明显不是的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var cs = "zero" ;
function innerFoo1 (){
console.log(cs);
}
function innerFoo2 (){
var cs = "one" ;
return innerFoo1;
}
//创建了两条作用域链(作用域链由函数作用域和全局作用域组成,全局作用域是作用域链的最顶端,变量查找的最后区域)
//全局作用域---> innerFoo1作用域--->
//全局作用域---> innerFoo2作用域--->
//作用域链在执行之前就已经创建
var ret = innerFoo2(); //返回的是 innerFoo1函数,这个函数执行的时候,其作用域链已经确定,innerFoo1函数作用域里没有cs变量,然后去全局作用域去找,找到了 zero
ret(); //zero

1.3 延长作用域链

1
2
3
try---catch
with

1.4 需要注意在if语句块和for语句块中声明的变量,是其所运行的环境的内的变量,如果在全局作用域运行for或者if,那么if和for中声明的变量就是全局window的属性,如果在函数内部运行if或者for循环,在if和for循环中声明的变量就是该函数作用域的局部变量;以下栗子,for也是一样的道理;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(true){
var color= "blue";
}
console.log(color);
function f1(){
if(true){
var color= "red";
}
console.log(color);
}
f1();
console.log(color);

2 变量声明提升

2.1”链式作用域” : 子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

1
2
3
4
5
6
7
8
9
10
11
12
var tmp = new Date();
function f() {
console.log(tmp);//undefined
if (false) { //由于是false不会进入,但是变量的声明还是会提升到函数体内,体内,体内的最上面(重三遍),所以下面在执行的时候会输出 undefined,
var tmp = "hello world";
//函数体内的声明会提升到 函数作用域 的最上面
}
console.log(tmp);//undefined
}
f();
console.log(tmp);//Tue Feb 28 2017 21:24:05 GMT+0800 (中国标准时间)
console.log(typeof tmp);//object
1
2
3
4
5
6
7
8
9
10
11
12
var tmp = new Date();
function f1() {
console.log(tmp);//undefined
if (true) { //true可以进入
var tmp = "hello world";//var 声明的变量会进行变量声明提升,只是提升到函数作用域最上面,并且只在函数作用域内有效
}
console.log(tmp);// hello world
}
f1();//函数执行的时候 ,输出 undefind 和 hello world
console.log(tmp);//Tue Feb 28 2017 21:24:05 GMT+0800 (中国标准时间)
console.log(typeof tmp);//object
1
2
3
4
5
6
7
8
9
10
11
var tmp = new Date();
function f2() {
console.log(tmp);//Tue Feb 28 2017 21:24:05 GMT+0800 (中国标准时间)
if (true) {
tmp = "hello world";//不是用var声明的变量,可以看做是全局的变量,当执行到这一行代码的时候,会改变全局变量tmp的值
}
console.log(tmp);// hello world
}
f2();
console.log(tmp);//hello world
console.log(typeof tmp);//string

2.2 变量名 形参 函数名重名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
console.log(typeof a);//funtion
function a(){} ;
var a=10;
//-----------------------------------------------
function foo(a){
console.log(typeof a);
var a =100;
function a(){};
}
foo(10); //输出function 如果变量名 形参 函数重名 ,还是函数优先
//-----------------------------------------------
function foo(a){
console.log(typeof a);
var a = "string";
}
foo(10); //输出number

3 执行上下文

3.1 函数每次的调用,都会形成一个执行上下文,每次调用都会形成一个新的执行上下文;

3.2 执行上下文 –> 变量对象(变量声明,函数声明,所有形参)+作用域链+this指向

3.3 生成执行上下文有三种情况

  • 全局环境:JavaScript代码运行起来会首先进入该环境
  • 函数环境:当函数被调用执行时,会进入当前函数中执行代码
  • eval
  • 每个函数都有自己的执行环境,当该函数之心完毕之后,该环境就会被销毁,保存在该函数环境中的变量和变量也会被销毁
    • 全局执行环境直到程序结束,例如关闭网页的时候才会被销毁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var color = 'blue';
function changeColor() {
var anotherColor = 'red';
function swapColors() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
}
swapColors();
}
changeColor();

执行上下文

3.4 执行上下文有以下特性:

  • 单线程
  • 同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待
  • 全局上下文只有唯一的一个,它在浏览器关闭时出栈
  • 函数的执行上下文的个数没有限制
  • 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。

3.5 执行上下文的生命周期

  • 创建阶段
    在这个阶段中,执行上下文会分别创建变量对象,建立作用域链,以及确定this的指向

  • 代码执行阶段
    创建完成之后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其他代码。

    ExcutedContent

3.6 变量对象的创建

变量对象(Variable Object)

变量对象的创建,依次经历了以下几个过程。

  1. 建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。

  2. function声明 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。

  3. var声明 检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。

    变量对象的创建过程

    变量对象在代码执行的时候会变成活动对象

    4 走个demo

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function test() {
    console.log(foo);
    console.log(bar);
    var foo = 'Hello';
    console.log(foo);
    var bar = function () {
    return 'world';
    }
    function foo() {
    return 'hello';
    }
    }
    test();
    1
    2
    3
    4
    5
    6
    7
    // 创建阶段 这个变量对象创建的过程,变量声明提升其实就是变量对象创建过程中完成的
    VO = {
    arguments: {...},
    foo: <foo reference>,
    bar: undefined
    }
    // 这里有一个需要注意的地方,因为var声明的变量当遇到同名的属性时,会跳过而不会覆盖
    1
    2
    3
    4
    5
    6
    7
    // 执行阶段 执行阶段,变量对象--->活动对象
    VO -> AO
    VO = {
    arguments: {...},
    foo: 'Hello',
    bar: <bar reference>
    }